#include <type.h>
#include <log.h>
#include <arch/io.h>
#include <common.h>
#include <tty/tty.h>

#define WHITEBLACKWORD 0x0700

#define CRTPORT 0x3d4
static u16* graph_buf = (u16*)VGA_BUF_PA;

#define TTY_DEFAULT_THEME 0

u16 tty_theme = TTY_DEFAULT_THEME;

void tty_clear() {
    asm volatile("rep stosw" ::"D"(graph_buf),
        "c"(VGA_LINE_NUM * VGA_LINE_LENGTH),
        "a"(tty_theme)
        : "memory");
}

void tty_init(u16* fb) {
    graph_buf = (u16*)fb;
    tty_clear();

    outb(0x3D4, 0x0A);
    outb(0x3D5, 13);
    outb(0x3D4, 0x0B);
    outb(0x3D5, 15);
}

static void putc(char c, u32 color) {
    u32 pos;

    outb(CRTPORT, 14);
    pos = inb(CRTPORT + 1) << 8;
    outb(CRTPORT, 15);
    pos |= inb(CRTPORT + 1);

    if (c == '\n') {
        pos += VGA_LINE_LENGTH - pos % VGA_LINE_LENGTH;
    } else if (c == '\b') {
        graph_buf[pos] = ' ' | 0x0700;
        if (pos > 0)
            --pos;
    } else {
        graph_buf[pos++] = (c & 0xff) | color;
    }

    if (pos >= VGA_BUF_SIZE) {
        for (int i = 0; i < VGA_BUF_SIZE - 1; i++)
            graph_buf[i] = graph_buf[i + VGA_LINE_LENGTH];

        for (int i = VGA_BUF_SIZE - 1; i < VGA_BUF_SIZE; i++)
            graph_buf[i] = color | ' ';

        pos -= VGA_LINE_LENGTH;
    }

    outb(CRTPORT, 14);
    outb(CRTPORT + 1, pos >> 8);
    outb(CRTPORT, 15);
    outb(CRTPORT + 1, pos);
    graph_buf[pos] = ' ' | 0x0700;
}

int tty_write(u32 minor, char* buf, int count) {
    for (int i = 0; i < count; i++) {
        putc(buf[i], WHITEBLACKWORD);
    }
    return count;
}

void tty_set_theme(u16 fg, u16 bg) {
    tty_theme = (bg << 4 | fg) << 8;
}

void tty_set_cursor(u32 x, u32 y) {
    if (x >= VGA_LINE_LENGTH || y >= VGA_LINE_NUM) {
        x = y = 0;
    }
    u32 pos = y * VGA_LINE_LENGTH + x;
    outb(CRTPORT, 14);
    outb(CRTPORT + 1, pos >> 8);
    outb(CRTPORT, 15);
    outb(CRTPORT + 1, pos);
}

void tty_flush_buffer(rbuffer_t* buf) {
    int x = 0, y = 0;
    tty_clear();

    char c;
    int state = 0;
    int g[2] = { 0, 0 };
    u16 current_theme = tty_theme;
    while (rbuffer_readone_async(buf, &c)) {
        if (state == 0 && c == '\033') {
            state = 1;
        } else if (state == 1 && c == '[') {
            state = 2;
        } else if (state > 1) {
            if ('0' <= c && c <= '9') {
                g[state - 2] = (c - '0') + g[state - 2] * 10;
            } else if (c == ';' && state == 2) {
                state = 3;
            } else {
                if (g[0] == 0 && g[1] == 0) {
                    current_theme = tty_theme;
                } else {
                    current_theme = (g[1] << 4 | g[0]) << 8;
                }
                g[0] = 0;
                g[1] = 0;
                state = 0;
            }
        } else {
            state = 0;
            switch (c) {
            case '\t':
                x += 4;
                break;
            case '\n':
                y++;
                // fall through
            case '\r':
                x = 0;
                break;
            default:
                *(graph_buf + x + y * VGA_LINE_LENGTH) =
                    (current_theme | c);
                (x)++;
                break;
            }

            if (x >= VGA_LINE_LENGTH) {
                x = 0;
                y++;
            }
            if (y >= VGA_LINE_NUM) {
                y--;
                break;
            }
        }
    }
    tty_set_cursor(x, y);
}